/* SharePoint Console Project
 * Http://SharePointConsole.codeplex.com
 * Copyright Jean-Alain Baeyens (2009)
 * Software under Microsoft Reciprocal License (Ms-RL)
 */

using System;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Text;
using Microsoft.SharePoint;
using System.Collections.Generic;
using Microsoft.SharePoint.StsAdmin;

using Sharepoint.Console.Entities;
using Sharepoint.Console.Commands;
using Sharepoint.Console.Enumerations;
using Sharepoint.Console.StsAdm;
using Microsoft.SharePoint.Administration;


namespace Sharepoint.Console
{
    class Program
    {
        private static Connection sharepointConnection = new Connection();
        private static bool isRecursing = false;
        private static Prompt promptType = Prompt.Standard;
        private const string SPACE_REPLACEMENT_VALUE = "%32%";
        private static List<Assembly> plugins = new List<Assembly>();

        /// <summary>
        /// Main procedure
        /// </summary> 
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            bool notExitResuested = true;
            string cmdString;

            try
            {
                writeConsoleHeader();

                if (args.Length > 1)
                {
                    System.Console.WriteLine("Invalide number of arguments. Syntaxe: SharepointConsole ['filename']");
                }

                else
                {
                    loadPlugIn();

                    if (args.Length == 1) run(string.Format("Run {0}", args[1]), sharepointConnection);

                    while (notExitResuested)
                    {
                        cmdString = promptAndExecute(ref notExitResuested);
                    }
                }
            }

            catch (Exception ex)
            {
                UnrecoverableErrorHandling(ex);
            }

            finally
            {
                ISharepointCommande cmd = new Disconnect();
                cmd.SharepointConnection = sharepointConnection;
                cmd.Execute(null);
            }

        }

        /// <summary>
        /// Load plugin assemblies
        /// </summary>
        private static void loadPlugIn()
        {
            string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
            appPath = (new Uri(appPath)).LocalPath;
            foreach (string file in Directory.GetFiles(string.Format(@"{0}\plugin",appPath), "*.dll"))
            {
                System.Console.WriteLine(string.Format("loading {0}", file));
                plugins.Add(Assembly.LoadFrom(file));
            }
            System.Console.WriteLine();
        }


        /// <summary>
        /// Display unrecoverable exception in console
        /// </summary>
        /// <param name="ex"></param>
        private static void UnrecoverableErrorHandling(Exception ex)
        {
            System.Console.WriteLine(ex.Message);
            System.Console.WriteLine("");
            System.Console.WriteLine("Unknow error. Application must shutdown");
            System.Console.WriteLine("Press enter to close application");
            System.Console.ReadLine();
        }

        /// <summary>
        /// Prompt next statement and execute it.
        /// </summary>
        /// <param name="notExitResuested"></param>
        /// <returns></returns>
        private static string promptAndExecute(ref bool notExitResuested)
        {
            string cmdString;
            displayPrompt();
            isRecursing = false;
            cmdString = System.Console.ReadLine();
            notExitResuested = !run(cmdString, sharepointConnection);
            return cmdString;
        }

        /// <summary>
        /// display the requested prompt
        /// </summary>
        private static void displayPrompt()
        {
            switch (promptType)
            {
                case Prompt.Site:
                    if (sharepointConnection.Site != null)
                    {
                        System.Console.Write(string.Format("{0}>", sharepointConnection.Site.Url));
                    }
                    else
                    {
                        System.Console.Write("Sharepoint>");
                    }
                    break;

                case Prompt.Web:
                    if (sharepointConnection.Web != null)
                    {
                        System.Console.Write(string.Format("{0}>", sharepointConnection.Web.Url));
                    }
                    else
                    {
                        System.Console.Write("Sharepoint>");
                    }
                    break;

                default:
                    System.Console.Write("Sharepoint>");
                    break;
            }
        }

        /// <summary>
        /// Write console header at startup
        /// </summary>
        private static void writeConsoleHeader()
        {
            System.Console.WriteLine(string.Format("Sharepoint Console Version {0}"
                 , Assembly.GetExecutingAssembly().GetName().Version.ToString()));
            System.Console.WriteLine("Http://SharePointConsole.codeplex.com");
            System.Console.WriteLine("");
            System.Console.WriteLine("Copyright Jean-Alain Baeyens (2009)");
            System.Console.WriteLine("");
            System.Console.WriteLine("This software is distributed under Microsoft Reciprocal License (Ms-RL)");
            System.Console.WriteLine("No warranty of any kind are offered with this software.");
            System.Console.WriteLine("You use it at your own risks. Read term of license before using it.");
            System.Console.WriteLine("");
        }

        /// <summary>
        /// Execute statement
        /// </summary>
        /// <param name="cmdString">Statement to execute</param>
        /// <param name="conn">Active SharePoint connection</param>
        /// <returns>execution result status</returns>
        private static bool run(string cmdString, Connection conn)
        {
            bool exitRequested = false;
            string[] cmdArgs;

            cmdArgs = splitArgs(cmdString);

            switch (cmdArgs[0].ToLower())
            {
                case "exit":
                    exitRequested = exitApplicationRequest(conn);
                    break;

                case "prompt":
                    promptChange(cmdArgs);
                    break;

                case "run":
                    exitRequested = executeScript(conn, cmdArgs);
                    break;

                case "load":
                    loadExternalAssembly(cmdArgs);
                    break;

                case "recursive":
                    callRecursion(cmdString, conn, cmdArgs);
                    break;

                case "help":
                    if (cmdArgs.Length == 1)
                    {
                        commandsList(conn);
                    }
                    else
                    {
                        helpOnCommand(conn, cmdArgs);
                    }
                    
                    break;

                default:
                    executeCommand(conn, cmdArgs);
                    break;
            }

            return exitRequested;
        }

        private static void loadExternalAssembly(string[] cmdArgs)
        {
            if (cmdArgs.Length == 2)
            {
                if (File.Exists(cmdArgs[1]))
                {
                    plugins.Add(Assembly.LoadFrom(cmdArgs[1]));
                    System.Console.WriteLine(string.Format("{0} loaded", cmdArgs[1]));
                }
                else
                {
                    System.Console.WriteLine("File not found");
                }
            }
            else
            {
                System.Console.WriteLine("Invalid syntax for load. Use load assemblyName");
            }
        }

        /// <summary>
        /// Execute command on this level and call recursion on all sublevel
        /// </summary>
        /// <param name="cmdString"></param>
        /// <param name="conn"></param>
        /// <param name="cmdArgs"></param>
        private static void callRecursion(string cmdString, Connection conn, string[] cmdArgs)
        {
            string[] effectiveCmdArgs = new string[cmdArgs.Length - 1];
            for (int i = 0; i < cmdArgs.Length - 1; i++)
            {
                effectiveCmdArgs[i] = cmdArgs[i + 1];
            }
            executeCommand(conn, effectiveCmdArgs);

            // recall command for all sub levels
            isRecursing = true;
            foreach (SPWeb web in conn.Web.GetSubwebsForCurrentUser())
            {
                Connection subLevelConn = new Connection();
                subLevelConn.Site = web.Site;
                subLevelConn.Web = web;
                run(cmdString, subLevelConn);
            }
            isRecursing = false;
        }

        /// <summary>
        /// Change prompt message
        /// </summary>
        /// <param name="cmdArgs"></param>
        private static void promptChange(string[] cmdArgs)
        {
            if (cmdArgs.Length > 1)
            {
                switch (cmdArgs[1].ToLower())
                {
                    case "standard":
                        promptType = Prompt.Standard;
                        break;
                    case "site":
                        promptType = Prompt.Site;
                        break;
                    case "web":
                        promptType = Prompt.Web;
                        break;
                    default:
                        System.Console.WriteLine("Usage: Prompt Standard/Site/Web");
                        break;
                }
            }
            else
            {
                System.Console.WriteLine("Usage: Prompt Standard/Site/Web");
            }
        }

        /// <summary>
        /// Exit operations
        /// </summary>
        /// <param name="conn"></param>
        /// <returns></returns>
        private static bool exitApplicationRequest(Connection conn)
        {
            disconnectBeforeExit(conn);
            return true;
        }

        /// <summary>
        /// Split command string
        /// </summary>
        /// <param name="cmdString"></param>
        /// <returns></returns>
        private static string[] splitArgs(string cmdString)
        {
            char[] separators = { ' ' };
            string stringDelimiter = "\"";
            string[] cmdArgs;


            if (cmdString.Contains(stringDelimiter))
            {
                StringBuilder tmpString = prepareStringToBeSplit(cmdString, stringDelimiter);

                cmdArgs = tmpString.ToString().Split(separators, StringSplitOptions.RemoveEmptyEntries);
                restoreArgsOriginalValue(cmdArgs);
            }

            else
            {
                cmdArgs = cmdString.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            }

            return cmdArgs;
        }

        /// <summary>
        /// Replace space included into dubblequote by a special string. 
        /// It's necessary before split string with space as delimiter
        /// </summary>
        /// <param name="cmdString"></param>
        /// <param name="stringDelimiter"></param>
        /// <returns></returns>
        private static StringBuilder prepareStringToBeSplit(string cmdString, string stringDelimiter)
        {
            StringBuilder tmpString = new StringBuilder();
            int startPos = 0;
            int pos;

            pos = cmdString.IndexOf(stringDelimiter, startPos);
            while (pos != -1)
            {
                tmpString.Append(cmdString.Substring(startPos, pos - startPos));
                startPos = pos;
                pos = cmdString.IndexOf(stringDelimiter, startPos + 1);

                if (pos != -1)
                {
                    tmpString.Append(cmdString.Substring(startPos, pos + 1 - startPos).Replace(" ", SPACE_REPLACEMENT_VALUE));
                    startPos = pos + 1;
                    pos = cmdString.IndexOf(stringDelimiter, startPos);
                }

                else
                {
                    tmpString.Append(cmdString.Substring(startPos).Replace(" ", SPACE_REPLACEMENT_VALUE));
                }
            }
            tmpString.Append(cmdString.Substring(startPos));
            return tmpString;
        }

        /// <summary>
        /// replace space replacement value by a real space.
        /// </summary>
        /// <param name="cmdArgs"></param>
        private static void restoreArgsOriginalValue(string[] cmdArgs)
        {
            int i = 0;
            while (i < cmdArgs.Length)
            {
                cmdArgs[i] = cmdArgs[i].Replace(SPACE_REPLACEMENT_VALUE, " ");

                if (cmdArgs[i].StartsWith("\"") && cmdArgs[i].EndsWith("\""))
                {
                    cmdArgs[i] = cmdArgs[i].Substring(1, cmdArgs[i].Length - 2);
                }

                i++;
            }
        }

        /// <summary>
        /// Execute statement requested
        /// </summary>
        /// <param name="conn">Connection to SharePoint</param>
        /// <param name="cmdArgs">Parameters string</param>
        private static void executeCommand(Connection conn, string[] cmdArgs)
        {
            Type cmdType;
            cmdType = getType(cmdArgs);

            if (cmdType == null)
            {
                System.Console.WriteLine("Invalid command");
            }

            else
            {
                try
                {
                    ISharepointCommande cmd = loadCommand(conn, cmdArgs, cmdType);

                    if (!isRecursing || cmd.IsRecursionAllowed)
                    {
                        cmd.SharepointConnection = conn;
                        System.Console.WriteLine(cmd.Execute(cmdArgs));
                    }
                    else
                    {
                        System.Console.WriteLine("Recursive call not allowed");
                    }
                }

                catch (Exception ex)
                {
                    System.Console.WriteLine(ex.Message);
                }
            }
        }

        private static ISharepointCommande loadCommand(Connection conn, string[] cmdArgs, Type cmdType)
        {
            ISharepointCommande cmd;
            if (InterfaceImplemented(cmdType, "ISPStsadmCommand"))
            {
                cmd = loadISPStsadmCommand(cmdArgs, cmdType);
            }
            else
            {
                if (InterfaceImplemented(cmdType, "ISPOperation"))
                {
                    cmd = loadSPOperationCommand(cmdArgs, cmdType);
                }
                else
                {
                    cmd = loadStandardCommand(conn, cmdArgs, cmdType);
                }
            }
            return cmd;
        }

        /// <summary>
        /// Load commands that are specificly designed for this tools
        /// </summary>
        /// <param name="conn">Connection to SharePoint</param>
        /// <param name="cmdArgs">Parameters string</param>
        /// <param name="cmdType">Command type</param>
        private static Sharepoint.Console.Commands.ISharepointCommande loadStandardCommand(Connection conn, string[] cmdArgs, Type cmdType)
        {
            return (ISharepointCommande)Activator.CreateInstance(cmdType);
        }

        /// <summary>
        /// Load command that have SPOperation as bas class. This is for internal StsdAdm commands.
        /// </summary>
        /// <param name="cmdArgs">parameters string</param>
        /// <param name="cmdType">command type</param>
        private static Sharepoint.Console.Commands.ISharepointCommande loadSPOperationCommand(string[] cmdArgs, Type cmdType)
        {
            object[] parameters = { new SPGlobalAdmin() };
            object stsAdmCmd = Activator.CreateInstance(cmdType, parameters);
            StsAdmExtensionEncapsulation effectiveCmd;
            effectiveCmd = new StsAdmExtensionEncapsulation(cmdArgs[0], stsAdmCmd, false);
            return (ISharepointCommande)effectiveCmd;
        }

        /// <summary>
        /// Execute command implementing ISPStsAdmCommand
        /// </summary>
        /// <param name="cmdArgs">parameters string</param>
        /// <param name="cmdType">command type</param>
        private static Sharepoint.Console.Commands.ISharepointCommande loadISPStsadmCommand(string[] cmdArgs, Type cmdType)
        {
            ISPStsadmCommand stsAdmCmd = (ISPStsadmCommand)Activator.CreateInstance(cmdType);
            StsAdmExtensionEncapsulation effectiveCmd;
            effectiveCmd = new StsAdmExtensionEncapsulation(cmdArgs[0], stsAdmCmd, false);
            return (ISharepointCommande)effectiveCmd;
        }

        /// <summary>
        /// get the type for the requested command
        /// </summary>
        /// <param name="cmdArgs">parameters</param>
        /// <returns></returns>
        private static Type getType(string[] cmdArgs)
        {
            Type cmdType = null;
            //cmdType = Type.GetType(string.Format("Sharepoint.Console.Commands.{0}", cmdArgs[0]), false, true);
            Type[] types = Assembly.GetExecutingAssembly().GetTypes();
            foreach (Type item in types)
            {
                if (item.Name.ToLower() == cmdArgs[0].ToLower() &&
                    (InterfaceImplemented(item, "ISPStsadmCommand") 
                    || InterfaceImplemented(item, "ISharepointCommande")
                    || InterfaceImplemented(item, "ISPOperation")))
                {
                    cmdType = item;
                    break;
                }
            }

            if (cmdType == null)
            {
                cmdType = getTypeFromAddIns(cmdArgs, cmdType);
            }
            return cmdType;
        }

        /// <summary>
        /// Get the requested type into loaded addins.
        /// </summary>
        /// <param name="cmdArgs"></param>
        /// <param name="cmdType"></param>
        /// <returns></returns>
        private static Type getTypeFromAddIns(string[] cmdArgs, Type cmdType)
        {
            foreach (Assembly plugin in plugins)
            {
                Type[] types = plugin.GetTypes();
                foreach (Type item in types)
                {
                    if (item.Name.ToLower() == cmdArgs[0].ToLower()
                     && (InterfaceImplemented(item, "ISPStsadmCommand")
                    || InterfaceImplemented(item, "ISharepointCommande")
                    || InterfaceImplemented(item, "ISPOperation")))
                    {
                        cmdType = item;
                        break;
                    }
                }
                if (cmdType != null)
                        return cmdType;
            }
            return null;
        }

        /// <summary>
        /// Verify if type is implemented
        /// </summary>
        /// <param name="cmdType"></param>
        /// <returns></returns>
        private static bool InterfaceImplemented(Type cmdType, string name)
        {
            // "ISharepointCommande","ISPStsadmCommand","ISPOperation"

            foreach (Type interfaceType in cmdType.GetInterfaces())
            {
                if (interfaceType.Name == name) 
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Disconnect from SharePoint connection. Use standard Disconnect command to do this.
        /// </summary>
        /// <param name="conn"></param>
        private static void disconnectBeforeExit(Connection conn)
        {
            ISharepointCommande cmd = null;
            cmd = new Disconnect();
            cmd.SharepointConnection = conn;
            cmd.Execute(null);
        }

        /// <summary>
        /// Ecxecute a scipt
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="cmdArgs"></param>
        /// <returns></returns>
        private static bool executeScript(Connection conn, string[] cmdArgs)
        {
            bool exitRequested = false;
            bool scriptEnded = false;

            if (cmdArgs.Length == 2)
            {

                if (File.Exists(cmdArgs[1]))
                {
                    FileStream script = new FileStream(cmdArgs[1], FileMode.Open);
                    StreamReader reader = new StreamReader(script);

                    string cmdString;

                    while (!exitRequested && !scriptEnded)
                    {
                        cmdString = reader.ReadLine();
                        if (cmdString == null)
                        {
                            scriptEnded = true;
                        }

                        else
                        {
                            System.Console.WriteLine(cmdString);
                            exitRequested = run(cmdString, conn);
                        }
                    }

                    reader.Close();
                    script.Close();
                }

                else
                {
                    System.Console.WriteLine("File not found.");
                }

            }

            else
            {
                System.Console.WriteLine("Invalide number of arguments. Syntaxe: run 'filename'");
            }

            return exitRequested;
        }

        private static void helpOnCommand(Connection conn, string[] cmdArgs)
        {
            Type cmdType;
            string[] args = new string[1];
            args[0] = cmdArgs[1];
            cmdType = getType(args);

            if (cmdType == null)
            {
                System.Console.WriteLine("Invalid command");
            }

            else
            {
                try
                {
                    ISharepointCommande cmd = loadCommand(conn, cmdArgs, cmdType);
                    System.Console.WriteLine("Summary:");
                    System.Console.WriteLine(cmd.ShortHelp);
                    System.Console.WriteLine("");
                    System.Console.WriteLine("Description:");
                    System.Console.WriteLine(cmd.LongHelp);
                    System.Console.WriteLine("");
                    System.Console.WriteLine("Syntax:");
                    System.Console.WriteLine(cmd.Syntax);
                }

                catch (Exception ex)
                {
                    System.Console.WriteLine(ex.Message);
                }
            }
        }

        private static void commandsList(Connection conn)
        {
            foreach (Assembly plugin in plugins)
            {
                Type[] types = plugin.GetTypes();
                foreach (Type item in types)
                {
                    if (InterfaceImplemented(item, "ISPStsadmCommand")
                    || InterfaceImplemented(item, "ISharepointCommande")
                    || InterfaceImplemented(item, "ISPOperation"))
                    {
                        string[] cmdString = {item.Name};
                        ISharepointCommande cmd = loadCommand(conn, cmdString, item);
                        System.Console.WriteLine(string.Format("{0} \t\t {1}",item.Name,cmd.ShortHelp));
                    }
                }
            }
        }

    }
}
